import 'dart:async';
import 'dart:io';

import 'package:archive/archive_io.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
import 'package:shared_preferences/shared_preferences.dart';

/// 表示 SDK 相关设置的数据对象。
///
/// - [currentSdkPath] 当前选择使用的 SDK 目录绝对路径；可能为 `null`（未选择）
/// - [installedSdkPaths] 已安装/可用的 SDK 目录集合
///
/// 此类为不可变（`final` 字段），配合 [copyWith] 以不可变方式更新。
class SdkSettings {
  /// 当前使用的 SDK 目录路径；可能为 `null`。
  final String? currentSdkPath;

  /// 已安装的 SDK 目录列表。
  final List<String> installedSdkPaths;

  /// 创建一个 [SdkSettings]。
  const SdkSettings({this.currentSdkPath, this.installedSdkPaths = const []});

  /// 返回一个基于当前实例的新副本。
  ///
  /// 仅对提供的参数进行更新，未提供的字段将复用旧值。
  SdkSettings copyWith({
    String? currentSdkPath,
    List<String>? installedSdkPaths,
  }) {
    return SdkSettings(
      currentSdkPath: currentSdkPath ?? this.currentSdkPath,
      installedSdkPaths: installedSdkPaths ?? this.installedSdkPaths,
    );
  }
}

/// 表示一个可供下载/安装的 SDK 发布包（release）。
///
/// - [version] 版本号展示字符串（例如 `1.2.3`）
/// - [url] 该版本发布包的下载地址
class SdkRelease {
  /// 版本号。
  final String version;

  /// 发布包下载地址。
  final Uri url;

  /// 创建一个 [SdkRelease]。
  const SdkRelease({required this.version, required this.url});

  /// 不包含扩展名的文件名推导。
  ///
  /// - 兼容多级压缩后缀（如 `.tar.gz`、`.tar.bz2`、`.tgz`）
  /// - 回退到单级扩展名的去除逻辑
  String get fileNameWithoutExt {
    var name = p.basename(url.path);

    // 常见多级后缀先判断
    if (name.endsWith('.tar.gz')) {
      return name.substring(0, name.length - '.tar.gz'.length);
    }
    if (name.endsWith('.tar.bz2')) {
      return name.substring(0, name.length - '.tar.bz2'.length);
    }
    if (name.endsWith('.tgz')) {
      return name.substring(0, name.length - '.tgz'.length);
    }

    // 常见单级后缀
    return p.basenameWithoutExtension(name);
  }

  /// 含扩展名的文件名（源自下载 URL 的路径部分）。
  String get fileName => p.basename(url.path);
}

/// 控制与驱动 SDK 设置与安装流程的 `ChangeNotifier`。
///
/// 典型使用：
/// 1. 构造时注入 [releases]、默认安装目录与可选的 SDK 目录校验器 [sdkDirValidator]；
/// 2. UI 订阅 [notifyListeners] 更新，展示下载进度、错误、选择列表等；
/// 3. 调用 [pickLocalSdk] 选择已有 SDK；
/// 4. 调用 [downloadAndInstall] 下载并解压选择的 [selectedRelease]；
/// 5. 使用 [saveSettings]/[loadSettings] 持久化/恢复设置状态。
class SdkSettingsController extends ChangeNotifier {
  /// 当前内存中的设置数据。
  SdkSettings data;

  /// SDK 安装根目录（用于下载/解压）。
  Directory? installDir;

  /// 当前在 UI 中选中的待安装发行版。
  SdkRelease? selectedRelease;

  /// 下载进度（0.0 ~ 1.0）。
  double progress = 0.0;

  /// 是否处于下载中。
  bool isDownloading = false;

  /// 下载或安装错误信息（若有）。
  String? downloadError;

  /// 可用的发行版列表（通常来自远端索引或本地配置）。
  final List<SdkRelease> releases;

  /// 默认安装根目录（若未提供则使用用户家目录下 `~/.sdks`）。
  final Directory? defaultInstallDir;

  /// SDK 目录校验器：用于自定义判定某目录是否是“有效的 SDK 根目录”。
  ///
  /// 未提供时，默认判定规则为目录下应包含 `bin/` 与 `lib/` 子目录。
  final FutureOr<bool> Function(Directory dir)? sdkDirValidator;

  /// 创建一个 [SdkSettingsController]。
  ///
  /// - [releases] 必填：可选的发行版列表
  /// - [defaultInstallDir] 可选：默认安装路径根
  /// - [sdkDirValidator] 可选：自定义 SDK 目录校验逻辑
  /// - [initial] 可选：初始设置数据
  SdkSettingsController({
    required this.releases,
    this.defaultInstallDir,
    this.sdkDirValidator,
    SdkSettings? initial,
  }) : data = initial ?? const SdkSettings() {
    // 默认选择第一个发行版（若存在）
    selectedRelease = releases.isNotEmpty ? releases.first : null;
    // 安装目录：优先使用传入默认值，否则 fallback 到用户家目录 ~/.sdks
    installDir = defaultInstallDir ?? _defaultSdkRoot();
  }

  /// 计算默认 SDK 根目录：`$HOME/.sdks`（跨平台兜底）。
  static Directory _defaultSdkRoot() {
    final home = Platform.environment['HOME'] ??
        Platform.environment['USERPROFILE'] ??
        '.';
    return Directory(p.join(home, '.sdks'));
  }

  /// 将当前 [data] 静默写入 SharedPreferences。
  ///
  /// - 采用 *fire-and-forget* 的 `async void`，避免阻塞 UI。
  Future<void> _persist() async {
    try {
      await SdkSettingsController.saveSettings(data);
    } catch (_) {
      // 写入失败时忽略，以免打断正常流程
    }
  }

  /// SharedPreferences 中“当前 SDK 路径”的键名。
  static const _kCurrentKey = 'sdk_current_path';

  /// SharedPreferences 中“已安装 SDK 列表”的键名。
  static const _kInstalledKey = 'sdk_installed_paths';

  /// 将 [SdkSettings] 持久化到本地（SharedPreferences）。
  ///
  /// - `currentSdkPath == null` 时以空字符串存储，读取时再转回 `null`
  static Future<void> saveSettings(SdkSettings s) async {
    final sp = await SharedPreferences.getInstance();
    await sp.setString(_kCurrentKey, s.currentSdkPath ?? '');
    await sp.setStringList(_kInstalledKey, s.installedSdkPaths);
  }

  /// 从本地（SharedPreferences）读取设置。
  ///
  /// 若未保存过任何数据（当前路径与列表均为空），返回 `null` 以便上层识别“首次启动”。
  static Future<SdkSettings?> loadSettings() async {
    final sp = await SharedPreferences.getInstance();
    final current = sp.getString(_kCurrentKey);
    final installed = sp.getStringList(_kInstalledKey) ?? [];

    // 两个都为空时视为没有持久化数据
    final hasAny =
        (current != null && current.isNotEmpty) || installed.isNotEmpty;
    if (!hasAny) return null;

    return SdkSettings(
      currentSdkPath: (current != null && current.isNotEmpty) ? current : null,
      installedSdkPaths: installed,
    );
  }

  // ---------- 业务逻辑 ----------

  /// 通过系统目录选择对话框选择一个**本地已存在**的 SDK 目录。
  ///
  /// - [context] 仅用于配合某些平台/插件的对话框展示
  /// - [onError] 当选择的目录不满足 SDK 结构时，回调错误消息
  ///
  /// 成功后会：
  /// 1. 更新 [data.currentSdkPath] 指向新目录；
  /// 2. 将目录加入 [data.installedSdkPaths]（去重）；
  /// 3. 调用 [notifyListeners] 通知 UI。
  Future<void> pickLocalSdk({
    required BuildContext context,
    required Function(String msg) onError,
  }) async {
    final dir = await FilePicker.platform.getDirectoryPath(
      dialogTitle: 'Select SDK directory',
      lockParentWindow: true,
    );
    if (dir == null) return; // 用户取消

    final d = Directory(dir);
    final valid = await isValidSdkDir(d);
    if (!valid) {
      onError('The selected directory is not a valid SDK.');
      return;
    }

    // 加入已安装集合并设为当前
    final setPaths = {...data.installedSdkPaths, dir}.toList();
    data = data.copyWith(currentSdkPath: dir, installedSdkPaths: setPaths);
    await _persist();           // ★ 写入持久化
    notifyListeners();
  }

  /// 校验给定目录是否为“有效 SDK 目录”。
  ///
  /// - 若提供了 [sdkDirValidator]，则委托给自定义逻辑；
  /// - 否则默认要求该目录下存在 `bin/` 与 `lib/` 子目录。
  Future<bool> isValidSdkDir(Directory dir) async {
    if (sdkDirValidator != null) {
      return await sdkDirValidator!(dir);
    }
    final bin = Directory(p.join(dir.path, 'bin'));
    final lib = Directory(p.join(dir.path, 'lib'));
    return await bin.exists() && await lib.exists();
  }

  /// 从“已安装列表”移除某个路径。
  ///
  /// 若移除的路径正是当前使用的 [data.currentSdkPath]，
  /// 则将当前路径回退为列表中的第一个元素（若存在），否则置为 `null`。
  void removeInstalled(String path) {
    final next = [...data.installedSdkPaths]..remove(path);
    data = data.copyWith(
      installedSdkPaths: next,
      currentSdkPath: data.currentSdkPath == path
          ? (next.isNotEmpty ? next.first : null)
          : data.currentSdkPath,
    );
    _persist();                 // ★ 写入持久化
    notifyListeners();
  }

  /// 选择一个安装根目录（用于下载解压到该目录）。
  ///
  /// 仅更新 [installDir]，不触发下载或解压。
  Future<void> selectInstallDir({required BuildContext context}) async {
    final dir = await FilePicker.platform.getDirectoryPath(
      dialogTitle: 'Select installation directory',
      lockParentWindow: true,
    );
    if (dir == null) return;
    installDir = Directory(dir);
    notifyListeners();
  }

  /// 下载并安装当前 [selectedRelease]。
  ///
  /// 流程：
  /// 1. 准备安装根目录（默认 `~/.sdks`）与临时保存文件路径；
  /// 2. 以流式方式下载文件，期间更新 [progress] 并 [notifyListeners]；
  /// 3. 解压到目标目录：`<installRoot>/Snow-<version>`；
  /// 4. 调用 [isValidSdkDir] 进行结构校验；
  /// 5. 成功后更新 [data] 的当前路径与已安装列表，并回调 [onSnack]；
  /// 6. 失败时填充 [downloadError] 并通知 UI；
  /// 7. 最终重置 [isDownloading] 与 [progress]。
  Future<void> downloadAndInstall({
    required Function(String msg) onSnack,
  }) async {
    if (selectedRelease == null) return;
    final release = selectedRelease!;
    final root = installDir ?? _defaultSdkRoot();

    isDownloading = true;
    progress = 0;
    downloadError = null;
    notifyListeners();

    try {
      if (!await root.exists()) {
        await root.create(recursive: true);
      }

      // 下载文件保存到安装根目录下
      final savePath = p.join(root.path, release.fileName);
      final file = File(savePath);

      final req = http.Request('GET', release.url);
      final resp = await req.send();

      final total = resp.contentLength ?? 0;
      int received = 0;

      // 边下边写，顺便更新进度
      final sink = file.openWrite();
      await for (final chunk in resp.stream) {
        received += chunk.length;
        sink.add(chunk);
        if (total > 0) {
          progress = received / total;
          notifyListeners(); // 通知 UI 刷新进度条
        }
      }
      await sink.close();

      // 解压目录：Snow-<version>
      final extractTo = Directory(p.join(root.path, 'Snow-${release.version}'));

      if (!await extractTo.exists()) {
        await extractTo.create(recursive: true);
      }
      await _extractArchive(file, extractTo);

      // 基本结构校验：防止误解压或包格式错误
      final valid = await isValidSdkDir(extractTo);
      if (!valid) {
        throw Exception('Extraction completed, but the structure is invalid.');
      }

      // 维护已安装列表与当前 SDK
      final paths = {...data.installedSdkPaths, extractTo.path}.toList();
      data = data.copyWith(
        currentSdkPath: extractTo.path,
        installedSdkPaths: paths,
      );
      await _persist();         // 写入持久化
      onSnack('SDK ${release.version} installed successfully.');
    } catch (e) {
      downloadError = '$e';
      notifyListeners();
    } finally {
      isDownloading = false;
      progress = 0;
      notifyListeners();
    }
  }

  /// 解压（支持 `.zip`、`.tar.gz`/`.tgz`、`.tar`）。
  ///
  /// - 使用 `archive` 包进行多格式解压；
  /// - `.tar.gz` 会先 `GZip` 解压再 Tar 解包；
  /// - 解压目标为 [to] 目录。
  ///
  /// 若后缀名不受支持，抛出 [UnsupportedError]。
  Future<void> _extractArchive(File archiveFile, Directory to) async {
    final lower = archiveFile.path.toLowerCase();
    final bytes = await archiveFile.readAsBytes();

    if (lower.endsWith('.zip')) {
      final archive = ZipDecoder().decodeBytes(bytes);
      extractArchiveToDisk(archive, to.path);
      return;
    }

    if (lower.endsWith('.tar.gz') || lower.endsWith('.tgz')) {
      final archive = TarDecoder().decodeBytes(
        GZipDecoder().decodeBytes(bytes),
      );
      extractArchiveToDisk(archive, to.path);
      return;
    }

    if (lower.endsWith('.tar')) {
      final archive = TarDecoder().decodeBytes(bytes);
      extractArchiveToDisk(archive, to.path);
      return;
    }

    throw UnsupportedError('Unsupported archive format: ${archiveFile.path}');
  }

  /// 设置当前正在使用的 SDK 目录路径（不做有效性校验）。
  void setCurrentSdkPath(String path) {
    data = data.copyWith(currentSdkPath: path);
    _persist();                 // 写入持久化
    notifyListeners();
  }

  /// 改变 UI 中选中的发行版。
  void setSelectedRelease(SdkRelease r) {
    selectedRelease = r;
    notifyListeners();
  }

  /// 在系统文件管理器中打开指定路径。
  ///
  /// - macOS: `open <path>`
  /// - Windows: `explorer <path>`
  /// - Linux: `xdg-open <path>`
  ///
  /// 若调用失败会被静默忽略。
  Future<void> revealInFileManager(String path) async {
    try {
      if (Platform.isMacOS) {
        await Process.run('open', [path]);
      } else if (Platform.isWindows) {
        await Process.run('explorer', [path.replaceAll('/', r'\')]);
      } else if (Platform.isLinux) {
        await Process.run('xdg-open', [path]);
      }
    } catch (_) {}
  }
}
